<template>
  <el-form v-bind="$attrs" :model="formModel" ref="form">
    <template v-for="(item, index) in _formItems">
      <slot v-if="item.slot" :name="item.slot" />

      <el-form-item
        :key="index"
        v-else-if="item._ifRender"
        v-show="item._ifShow"
        v-bind="item.itemAttrs"
        :prop="item.attrs.key"
      >
        <slot v-if="item.itemSlot" :name="item.itemSlot" />

        <component
          v-else
          :is="item.tag"
          v-model="formModel[item.attrs.key]"
          v-bind="item.attrs"
          v-on="item.listeners"
          v-bind-directive="item.directives"
        >
          <template v-for="tagSlot in item.tagSlots" :slot="tagSlot.name">
            <span v-if="!$slots[tagSlot.slot]" :key="tagSlot.slot">{{ tagSlot.slot }}</span>

            <slot v-else :name="tagSlot.slot"></slot>
          </template>
        </component>

        <slot v-if="item.appendSlot" :name="item.appendSlot"></slot>
      </el-form-item>
    </template>

    <el-form-item v-if="submit || dismiss">
      <el-button
        @click="handleSubmit"
        v-if="submit"
        type="primary"
        class="btn-long"
        :loading="$attrs.loading"
      >
        {{ $attrs.submitText || '保存' }}
      </el-button>
      <el-button @click="handleDismiss" v-if="dismiss" :disabled="$attrs.loading">
        {{ $attrs.dismissText || '取消' }}
      </el-button>
    </el-form-item>

    <slot></slot>
  </el-form>
</template>
<script>
import { tags } from './tags'
import { cloneDeep } from '@vimi/utils-tool'

export default {
  name: 'CForm',
  components: {},
  props: {
    formItems: {
      type: Array,
      required: true,
    },
    submit: {
      type: Boolean,
      default: true,
    },
    dismiss: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      formModel: {},
      originModel: {},
      syncAttrs: {},
    }
  },
  computed: {
    // 根据formItem计算出实际需要让页面渲染的真正的_formItem数据
    // this.formModel中的值改变触发computed
    _formItems() {
      return this.formItems.map(item => this.computeFormItem(item, this.formModel))
    },
  },
  watch: {
    // 使用watch观察父组件传入的formItems,初始化formModel对象(只调用一次)
    formItems: {
      handler: function () {
        this.formItems.forEach(formItem => {
          // 跳过没有key的属性(插槽)
          if (!formItem.attrs || !formItem.attrs.key) return

          // 因为组件内部声明的formModel是一个空对象,地Vue的响应式系统是监听不到对象创建了新的属性
          // 需要使用$set来设置,使得能够强制更新视图
          this.$set(this.formModel, formItem.attrs.key, formItem.attrs.value)
          this.originModel = cloneDeep(this.formModel)
        })
      },
      deep: true,
      immediate: true,
    },
  },
  created() {},
  mounted() {
    // mounted钩子中formItems是空数组,所以不在mounted里面操作
  },
  methods: {
    computeFormItem(formItem, model) {
      const item = cloneDeep(formItem)
      const key = item.attrs.key

      // 对应到组件映射表
      let basicItem = tags[item.tag || 'input']
      if (basicItem) item.tag = basicItem.component

      // 继承基类的属性
      item.attrs = Object.assign(
        {},
        (basicItem && basicItem.attrs) || {},
        item.attrs,
        this.syncAttrs[key]
      )

      // 获取动态 Attributes
      if (item.getAttrs) {
        const attrs = item.getAttrs(model)
        // 在computed中更新触发源formModel，是否会出现死循环有待观察
        if (attrs && attrs.value !== undefined) {
          this.$set(this.formModel, key, attrs.value)
        }
        item.attrs = Object.assign({}, item.attrs, attrs)
      }

      // 获取动态 itemAttrs，主要是为了动态验证表单
      if (item.getItemAttrs) {
        item.itemAttrs = Object.assign({}, item.itemAttrs, item.getItemAttrs(model))
      }

      // 条件渲染，不会加入表单提交字段中
      item._ifRender = item.ifRender ? item.ifRender(model) : true

      // 防止表单提交时存在多余 key
      if (!item._ifRender) {
        delete model[key]
      }

      // 隐藏表单，但会加入提交字段中
      item._ifShow =
        item.ifShow !== undefined
          ? typeof item.ifShow === 'function'
            ? item.ifShow(model)
            : !!item.ifShow
          : true

      // 事件监听
      if (item.events) {
        item.listeners = Object.keys(item.events).reduce((prev, curr) => {
          prev[curr] = (...args) => item.events[curr](this.formModel, ...args)
          return prev
        }, {})
      }

      return item
    },

    // 用ref调用的异步设置属性
    setAttrs(key, attrs = {}) {
      let val = attrs.value
      if (val === undefined) {
        val = this.formModel[key]
      }
      this.syncAttrs[key] = attrs
      this.$delete(this.formModel, key) // 不delete不能触发computed
      this.$set(this.formModel, key, val)
    },

    handleSubmit() {
      this.$refs.form.validate((valid, obj = {}) => {
        if (!valid) {
          let errMsg = '输入错误，请重新输入！'
          const errObj = Object.keys(obj)
          if (errObj.length) {
            const firstErrObj = obj[errObj[0]]
            errMsg = (firstErrObj.length && firstErrObj[0].message) || errMsg
          }
          return this.$message.error(errMsg)
        }

        this.$emit('submit', this.formModel, this._formItems)
      })
    },

    handleDismiss() {
      this.syncAttrs = {}
      this.formModel = cloneDeep(this.originModel)
      this.clearValidate()
      this.$emit('dismiss', this.formModel)
    },

    validateField(props, callback) {
      this.$refs.form.validateField(props, callback)
    },

    clearValidate() {
      this.$refs.form.clearValidate()
    },
  },
}
</script>
<style scoped></style>
